package com.uinnova.product.eam.service.impl;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.binary.core.util.BinaryUtils;
import com.binary.framework.exception.ServiceException;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.uinnova.product.eam.base.util.EamUtil;
import com.uinnova.product.eam.base.util.ExcelUtil;
import com.uinnova.product.eam.comm.model.es.AppSquareConfig;
import com.uinnova.product.eam.config.Env;
import com.uinnova.product.eam.model.CiQueryCdtExtend;
import com.uinnova.product.eam.model.VcCiClassInfoDto;
import com.uinnova.product.eam.model.diagram.*;
import com.uinnova.product.eam.model.dto.*;
import com.uinnova.product.eam.model.enums.AnalyseLeaf;
import com.uinnova.product.eam.model.enums.RltPositionEnum;
import com.uinnova.product.eam.model.vo.CiSimpleInfoVo;
import com.uinnova.product.eam.model.vo.EamAnalyseCiVo;
import com.uinnova.product.eam.model.vo.EamAnalyseMergeVo;
import com.uinnova.product.eam.model.vo.EamAnalyseTableVo;
import com.uinnova.product.eam.service.*;
import com.uinnova.product.eam.service.utils.VisualModelUtils;
import com.uinnova.product.vmdb.comm.model.ci.CCcCiClass;
import com.uinnova.product.vmdb.comm.model.ci.CcCi;
import com.uinnova.product.vmdb.comm.model.ci.CcCiAttrDef;
import com.uinnova.product.vmdb.comm.model.ci.CcCiClass;
import com.uinnova.product.vmdb.comm.model.rlt.CcCiRlt;
import com.uinnova.product.vmdb.comm.util.CommUtil;
import com.uinnova.product.vmdb.provider.ci.bean.CcCiClassInfo;
import com.uinnova.product.vmdb.provider.ci.bean.CcCiInfo;
import com.uinnova.product.vmdb.provider.rlt.bean.CcCiRltInfo;
import com.uino.api.client.cmdb.ICIClassApiSvc;
import com.uino.bean.cmdb.base.*;
import com.uino.bean.permission.base.SysUser;
import com.uino.dao.cmdb.ESVisualModelSvc;
import com.uino.service.cmdb.microservice.IRltClassSvc;
import com.uino.util.sys.SysUtil;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

/**
 * 数据分析
 *
 * @author zdh_c
 */
@Service
public class BmDataAnalyzeSvcImpl implements IBmDataAnalyzeSvc {

    @Resource
    private ESVisualModelSvc esVisualModelSvc;
    @Resource
    private ICIClassApiSvc ciClassApiSvc;
    @Resource
    private ICIRltSwitchSvc rltSwitchSvc;
    @Resource
    private ICISwitchSvc iciSwitchSvc;
    @Resource
    private IEamCiSvc eamCiSvc;
    @Resource
    private IRltClassSvc rltClassSvc;
    @Resource
    private AppSquareConfigSvc configSvc;
    @Resource
    private IEamCIClassApiSvc eamClassApiSvc;
    
    private static final String LINK_LIST = "linkList";
    private static final String STR_NUM = "序号";

    @Override
    public List<VcCiClassInfoDto> queryClassInfoByMetaModel(CiQueryCdtExtend cdt) {
        //1.获取当前使用的元模型信息
        ESVisualModel esVisualModel = esVisualModelSvc.getEnableModel(SysUtil.getCurrentUserInfo().getDomainId());
        if (BinaryUtils.isEmpty(esVisualModel.getJson())) {
            return Collections.emptyList();
        }
        Set<Long> vmClassIds = VisualModelUtils.getCiClassIds(esVisualModel);
        Set<Long> classIdList = new HashSet<>();
        if(!BinaryUtils.isEmpty(cdt.getCardId())){
            AppSquareConfig config = configSvc.getInfoById(cdt.getCardId());
            AnalyseConfigVo analyseConfig = JSON.parseObject(config.getConfigure(), AnalyseConfigVo.class);
            if(!BinaryUtils.isEmpty(analyseConfig.getData())){
                for (Map<String, String> each : analyseConfig.getData()) {
                    long classId = Long.parseLong(each.get("key"));
                    if(vmClassIds.contains(classId)){
                        classIdList.add(classId);
                    }
                }
            }
        }
        if (BinaryUtils.isEmpty(classIdList)) {
            return Collections.emptyList();
        }
        //查询分类信息及分类数量
        cdt.setClassIds(classIdList.toArray(new Long[0]));
        return eamCiSvc.queryCiCountByClass(cdt, LibType.DESIGN);
    }

    @Override
    public Map<String, Object> queryByCiCode(String rootCiCode, Integer position) {
        Map<String, Object> result = MapUtil.newHashMap(2);
        List<ESCIInfo> ciList = iciSwitchSvc.getCiByCodes(Lists.newArrayList(rootCiCode), null, LibType.DESIGN);
        ESCIInfo rootCiInfo = ciList.get(0);
        //增加元模型限制
        SysUser user = SysUtil.getCurrentUserInfo();
        ESVisualModel esVisualModel = esVisualModelSvc.getEnableModel(user.getDomainId());
        if(BinaryUtils.isEmpty(esVisualModel)){
            return Collections.emptyMap();
        }
        List<DiagramNodeLinkInfo> rltLinkList = VisualModelUtils.getRltClassIds(esVisualModel);
        List<String> modelRlt = rltLinkList.stream().map(DiagramNodeLinkInfo::getLinkKey).distinct().collect(Collectors.toList());
        //实体及属性分类
        List<String> classCodes = Lists.newArrayList(Env.ATTRIBUTES.getCode(), Env.CONCEPTION_ENTITY.getCode(), Env.LOGIC_ENTITY.getCode(), Env.PHYSICAL_ENTITY.getCode());
        List<CcCiClassInfo> classList = eamClassApiSvc.getByClassCodes(classCodes, user.getDomainId());
        Map<String, CcCiClass> classMap = classList.stream().map(CcCiClassInfo::getCiClass).collect(Collectors.toMap(CcCiClass::getClassCode, e -> e, (k1, k2) -> k2));
        CcCiClass attrClass = classMap.get(Env.ATTRIBUTES.getCode());
        CcCiClass conceptionClass = classMap.get(Env.CONCEPTION_ENTITY.getCode());
        CcCiClass logicClass = classMap.get(Env.LOGIC_ENTITY.getCode());
        CcCiClass physicalClass = classMap.get(Env.PHYSICAL_ENTITY.getCode());
        List<Long> entityClassIds = new ArrayList<>();
        if (conceptionClass != null) {
            entityClassIds.add(conceptionClass.getId());
        }
        if (logicClass != null) {
            entityClassIds.add(logicClass.getId());
        }
        if (physicalClass != null) {
            entityClassIds.add(physicalClass.getId());
        }
        Long attrClassId = 1l;
        if (attrClass != null) {
            attrClassId = attrClass.getId();
        }
        //左侧 查询源端
        List<RltDataAnalyzeDto> leftSourceList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(modelRlt) && RltPositionEnum.RIGHT.getVal() != position) {
            leftSourceList = queryAnalyze(rootCiCode, modelRlt, RltPositionEnum.LEFT.getVal());
            //填充实体属性
            this.setAttribute(leftSourceList, entityClassIds, attrClassId);
        }
        //右侧 查询目标端
        List<RltDataAnalyzeDto> rightTargetList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(modelRlt) && RltPositionEnum.LEFT.getVal() != position) {
            rightTargetList = queryAnalyze(rootCiCode, modelRlt, RltPositionEnum.RIGHT.getVal());
            //填充实体属性
            this.setAttribute(rightTargetList, entityClassIds, attrClassId);
        }
        //根节点是实体需要填入实体属性
        List<CcCiInfo> attrList = new ArrayList<>();
        if(entityClassIds.contains(rootCiInfo.getClassId())){
            if (attrClassId == null) {
                attrClassId = 1l;
            }
            List<CcCiRltInfo> rltInfoList = rltSwitchSvc.queryRltByCodes(Sets.newHashSet(rootCiCode), null, entityClassIds, Lists.newArrayList(attrClassId));
            attrList = rltInfoList.stream().map(CcCiRltInfo::getTargetCiInfo).distinct().collect(Collectors.toList());
        }
        result.put("sourceList", leftSourceList);
        result.put("targetList", rightTargetList);
        result.put("attrList", attrList);
        return result;
    }

    /**
     * 以根节点ci为原点查询该所属分类在元模型中的相关路径图
     * 1.查询该ci所属分类
     * 2.查询元模型中启用库的所有sheet页node及link数据
     * 3.查询所有对象分类信息及关系分类信息
     * 4.以根节点分类为原点向上游或下游查询所有路径
     * @param ciCode 根节点ciCode
     */
    @Override
    public AnalysePathDto queryPath(String ciCode) {
        SysUser user = SysUtil.getCurrentUserInfo();
        AnalysePathDto result = new AnalysePathDto();
        List<ESCIInfo> ciList = iciSwitchSvc.getCiByCodes(Collections.singletonList(ciCode), null, LibType.DESIGN);
        if(BinaryUtils.isEmpty(ciList)){
            return result;
        }
        ESVisualModel esVisualModel = esVisualModelSvc.getEnableModel(user.getDomainId());
        if(BinaryUtils.isEmpty(esVisualModel) || BinaryUtils.isEmpty(esVisualModel.getJson())){
            return result;
        }
        List<VisualModelJson> modelJsonList = JSON.parseArray(esVisualModel.getJson(), VisualModelJson.class);
        CCcCiClass classQuery = new CCcCiClass();
        classQuery.setDomainId(user.getDomainId());
        List<CcCiClassInfo> ciClassInfoList = ciClassApiSvc.queryClassByCdt(classQuery);
        List<CcCiClassInfo> rltClassInfoList = rltClassSvc.queryAllClasses(user.getDomainId());
        Map<Long, CcCiClass> ciClassMap = ciClassInfoList.stream().map(CcCiClassInfo::getCiClass).collect(Collectors.toMap(CcCiClass::getId, each -> each, (k1, k2) -> k2));
        Map<Long, CcCiClass> rltClassMap = rltClassInfoList.stream().map(CcCiClassInfo::getCiClass).collect(Collectors.toMap(CcCiClass::getId, each -> each, (k1, k2) -> k2));
        //以根节点为源端向下查询
        result.setDown(getAnalysePathByLeaf(ciList.get(0), modelJsonList, ciClassMap, rltClassMap, true));
        //以根节点为目标端向上查询
        result.setUp(getAnalysePathByLeaf(ciList.get(0), modelJsonList, ciClassMap, rltClassMap, false));
        return result;
    }

    private Map<String, Object> getAnalysePathByLeaf(ESCIInfo rootCiInfo, List<VisualModelJson> modelJsonList, Map<Long, CcCiClass> ciClassMap, Map<Long, CcCiClass> rltClassMap, boolean from){
        Map<String, Object> result = new HashMap<>(16);
        List<AnalyseNodeVo> nodeResult = new ArrayList<>();
        List<AnalyseLinkVo> linkResult = new ArrayList<>();
        Long classId = rootCiInfo.getClassId();
        AnalyseLeaf leaf = from?AnalyseLeaf.TO:AnalyseLeaf.FROM;
        Map<Long, List<AnalyzeVisualModelLink>> linkGroup = new HashMap<>();
        for (VisualModelJson sheetJson : modelJsonList) {
            Map<Long, VisualModelNode> keyMap = new HashMap<>();
            for (VisualModelNode node : sheetJson.getNodeDataArray()) {
                if(BinaryUtils.isEmpty(node.getClassId())){
                    continue;
                }
                keyMap.put(node.getKey(), node);
                if(!node.getClassId().equals(classId)){
                    continue;
                }
                //添加根节点,在最后去重
                AnalyseNodeVo rootAnalyseNode = EamUtil.copy(ciClassMap.get(classId), AnalyseNodeVo.class);
                rootAnalyseNode.setLeaf(AnalyseLeaf.FROM).setRoot(true).setLoc(node.getLoc()).setWidth(node.getWidth()).setHeight(node.getHeight());
                nodeResult.add(rootAnalyseNode);
            }
            for (VisualModelLink link : sheetJson.getLinkDataArray()) {
                VisualModelNode fromNode = keyMap.get(link.getFrom());
                VisualModelNode toNode = keyMap.get(link.getTo());
                if(BinaryUtils.isEmpty(link.getClassId()) || fromNode == null || toNode == null){
                    continue;
                }
                String code = fromNode.getClassId() + "_" + link.getClassId() + "_" + toNode.getClassId();
                AnalyzeVisualModelLink copy = EamUtil.copy(link, AnalyzeVisualModelLink.class);
                copy.setFromNode(fromNode);
                copy.setToNode(toNode);
                copy.setPath(code);
                copy.setFrom(fromNode.getClassId());
                copy.setTo(toNode.getClassId());
                Long groupKey = from ? fromNode.getClassId() : toNode.getClassId();
                linkGroup.computeIfAbsent(groupKey, key -> new ArrayList<>()).add(copy);
            }
        }

        List<AnalyzeVisualModelLink> analyzeList = new ArrayList<>();
        List<AnalyzeVisualModelLink> childLists = linkGroup.get(classId);
        this.filterLink(Sets.newHashSet(classId), linkGroup, analyzeList, new HashSet<>(), from);
        for (AnalyzeVisualModelLink link : analyzeList) {
            AnalyseLinkVo linkVo = EamUtil.copy(rltClassMap.get(link.getClassId()), AnalyseLinkVo.class);
            linkVo.setCode(link.getPath()).setFrom(link.getFrom()).setTo(link.getTo()).setLeaf(leaf).setPoints(link.getPoints());
            linkResult.add(linkVo);
            VisualModelNode modelNode = from?link.getToNode() : link.getFromNode();
            if(BinaryUtils.isEmpty(modelNode.getClassId())){
                continue;
            }
            AnalyseNodeVo node = EamUtil.copy(ciClassMap.get(modelNode.getClassId()), AnalyseNodeVo.class);
            node.setLeaf(leaf).setLoc(modelNode.getLoc()).setWidth(modelNode.getWidth()).setHeight(modelNode.getHeight());
            if(node.getId().equals(classId)){
                node.setRoot(true);
            }
            nodeResult.add(node);
        }
        Map<Long, AnalyseNodeVo> nodeMap = nodeResult.stream().collect(Collectors.toMap(CcCiClass::getId, each -> each, (k1, k2) -> k2));
        Map<String, AnalyseLinkVo> linkMap = linkResult.stream().collect(Collectors.toMap(AnalyseLinkVo::getCode, each -> each, (k1, k2) -> k2));
        result.put("nodeList", nodeMap.values());
        result.put(LINK_LIST,linkMap.values());
        return result;
    }

    private void filterLink(Set<Long> parentIds, Map<Long, List<AnalyzeVisualModelLink>> linkGroup, List<AnalyzeVisualModelLink> analyzeList, Set<Long> distinct, boolean from){
        Set<Long> childIds = new HashSet<>();
        for (Long classId : parentIds) {
            List<AnalyzeVisualModelLink> links = linkGroup.get(classId);
            if(distinct.contains(classId) || CollectionUtils.isEmpty(links)){
                continue;
            }
            distinct.add(classId);
            analyzeList.addAll(links);
            if(from){
                childIds.addAll(links.stream().map(VisualModelLink::getTo).collect(Collectors.toSet()));
            }else{
                childIds.addAll(links.stream().map(VisualModelLink::getFrom).collect(Collectors.toSet()));
            }
        }
        if(CollectionUtils.isEmpty(childIds)){
            return;
        }
        this.filterLink(childIds, linkGroup, analyzeList, distinct, from);
    }

    @Override
    public List<DataAnalyzeBatch> queryByPath(String ciCode, AnalyseLeaf leaf, List<String> path) {
        if(BinaryUtils.isEmpty(path)){
            return Collections.emptyList();
        }
        SysUser user = SysUtil.getCurrentUserInfo();
        List<ESCIInfo> ciList = iciSwitchSvc.getCiByCodes(Lists.newArrayList(ciCode), null, LibType.DESIGN);
        if(BinaryUtils.isEmpty(ciList)){
            return Collections.emptyList();
        }
        ESCIInfo rootCiInfo = ciList.get(0);
        List<VisualModelLink> pathList = path.stream().map(VisualModelLink::new).collect(Collectors.toList());
        Map<Long, List<VisualModelLink>> pathGroup;
        if(AnalyseLeaf.FROM.equals(leaf)){
            //查询根节点上游
            pathGroup = pathList.stream().collect(Collectors.groupingBy(VisualModelLink::getTo));
        }else{
            //查询根节点下游
            pathGroup = pathList.stream().collect(Collectors.groupingBy(VisualModelLink::getFrom));
        }
        Set<Long> sourceIdList = new HashSet<>();
        Set<Long> targetIdList = new HashSet<>();
        Set<Long> rltIdList = new HashSet<>();
        for (VisualModelLink analysePath : pathList) {
            sourceIdList.add(analysePath.getFrom());
            targetIdList.add(analysePath.getTo());
            rltIdList.add(analysePath.getClassId());
        }

        List<CcCiRltInfo> data = rltSwitchSvc.queryRltByClassIds(rltIdList, sourceIdList, targetIdList);
        if(CollectionUtils.isEmpty(data)){
            return Collections.emptyList();
        }
        List<CcCiClassInfo> rltClassList = rltClassSvc.queryAllClasses(user.getDomainId());
        List<String> classCodes = Lists.newArrayList(Env.ATTRIBUTES.getCode(), Env.CONCEPTION_ENTITY.getCode(), Env.LOGIC_ENTITY.getCode(), Env.PHYSICAL_ENTITY.getCode());
        List<CcCiClassInfo> classList = eamClassApiSvc.getByClassCodes(classCodes, user.getDomainId());
        Map<String, CcCiClass> classMap = classList.stream().map(CcCiClassInfo::getCiClass).collect(Collectors.toMap(CcCiClass::getClassCode, e -> e, (k1, k2) -> k2));
        CcCiClass attrClass = classMap.get(Env.ATTRIBUTES.getCode());
        CcCiClass conceptionClass = classMap.get(Env.CONCEPTION_ENTITY.getCode());
        CcCiClass logicClass = classMap.get(Env.LOGIC_ENTITY.getCode());
        CcCiClass physicalClass = classMap.get(Env.PHYSICAL_ENTITY.getCode());
        List<Long> entityClassIds = new ArrayList<>();
        if (conceptionClass != null) {
            entityClassIds.add(conceptionClass.getId());
        }
        if (logicClass != null) {
            entityClassIds.add(logicClass.getId());
        }
        if (physicalClass != null) {
            entityClassIds.add(physicalClass.getId());
        }
        Long attrClassId = 1l;
        if (attrClass != null) {
            attrClassId = attrClass.getId();
        }
        Map<Long, CcCiClassInfo> rltClassMap = rltClassList.stream().collect(Collectors.toMap(each -> each.getCiClass().getId(), each -> each, (k1, k2) -> k2));
        //组装：源端分类id-关系分类id-目标端分类id
        Map<String, List<CcCiRltInfo>> rltGroup = data.stream().collect(Collectors.groupingBy(
                each -> each.getSourceCiInfo().getCi().getClassId()+"_"+each.getCiRlt().getClassId()+"_"+each.getTargetCiInfo().getCi().getClassId()));
        List<VisualModelLink> queryPathList = new ArrayList<>();
        findNextPath(Collections.singleton(rootCiInfo.getClassId()), pathGroup, queryPathList, new HashSet<>(), !AnalyseLeaf.FROM.equals(leaf));

        List<DataAnalyzeBatch> resultList = analyseByPath(ciCode, queryPathList, rltGroup, rltClassMap, leaf, attrClassId);
        //去重
        Map<String, DataAnalyzeBatch> resultMap = resultList.stream().collect(Collectors.toMap(each -> each.getRlt().getCiCode(), each -> each, (k1, k2) -> k1));
        List<DataAnalyzeBatch> result = Lists.newArrayList(resultMap.values());
        this.setAnalyzeBatch(result, entityClassIds, attrClassId);
        return result;
    }

    private void setAnalyzeBatch(List<DataAnalyzeBatch> analyzeList, List<Long> entityClassIds, Long attrClassId){
        Set<String> entityCodes = new HashSet<>();
        Map<String, AtomicLong> sourceCountMap = new HashMap<>();
        Map<String, AtomicLong> targetCountMap = new HashMap<>();
        for (DataAnalyzeBatch each : analyzeList) {
            if(!each.getSourceCiCode().equals(each.getTargetCiCode())){
                sourceCountMap.computeIfAbsent(each.getSourceCiCode(), key -> new AtomicLong(0)).incrementAndGet();
                targetCountMap.computeIfAbsent(each.getTargetCiCode(), key -> new AtomicLong(0)).incrementAndGet();
            }
            if(entityClassIds.contains(each.getSourceCi().getCi().getClassId())){
                entityCodes.add(each.getSourceCiCode());
            }
            if(entityClassIds.contains(each.getTargetCi().getCi().getClassId())){
                entityCodes.add(each.getTargetCiCode());
            }
        }
        for (DataAnalyzeBatch each : analyzeList) {
            each.getSourceCi().setSourceCount(targetCountMap.getOrDefault(each.getSourceCiCode(), new AtomicLong(0)).get());
            each.getSourceCi().setTargetCount(sourceCountMap.getOrDefault(each.getSourceCiCode(), new AtomicLong(0)).get());
            each.getTargetCi().setTargetCount(sourceCountMap.getOrDefault(each.getTargetCiCode(), new AtomicLong(0)).get());
            each.getTargetCi().setSourceCount(targetCountMap.getOrDefault(each.getTargetCiCode(), new AtomicLong(0)).get());
        }
        if(CollectionUtils.isEmpty(entityCodes)){
            return;
        }
        if (attrClassId == null) {
            return;
        }
        List<CcCiRltInfo> rltInfoList = rltSwitchSvc.queryRltByCodes(entityCodes, null, entityClassIds, Lists.newArrayList(attrClassId));
        if(CollectionUtils.isEmpty(rltInfoList)){
            return;
        }
        Map<String, List<CcCiRltInfo>> rltGroup = rltInfoList.stream().collect(Collectors.groupingBy(e -> e.getCiRlt().getSourceCiCode()));
        for (DataAnalyzeBatch each : analyzeList) {
            List<CcCiRltInfo> sourceList = rltGroup.get(each.getSourceCiCode());
            if(!CollectionUtils.isEmpty(sourceList)){
                List<CcCiInfo> attrList = sourceList.stream().map(CcCiRltInfo::getTargetCiInfo).distinct().collect(Collectors.toList());
                each.getSourceCi().setAttrList(attrList);
            }
            List<CcCiRltInfo> targetList = rltGroup.get(each.getTargetCiCode());
            if(!CollectionUtils.isEmpty(targetList)){
                List<CcCiInfo> attrList = targetList.stream().map(CcCiRltInfo::getTargetCiInfo).distinct().collect(Collectors.toList());
                each.getTargetCi().setAttrList(attrList);
            }
        }
    }

    private List<DataAnalyzeBatch> analyseByPath(String ciCode, List<VisualModelLink> pathList, Map<String, List<CcCiRltInfo>> rltGroup,
                                                 Map<Long, CcCiClassInfo> rltClassMap, AnalyseLeaf leaf, Long attrClassId){
        List<DataAnalyzeBatch> result = new ArrayList<>();
        Map<String, List<DataAnalyzeBatch>> analyzeGroup = new HashMap<>();
        boolean from = AnalyseLeaf.FROM.equals(leaf);
        for (VisualModelLink rank : pathList) {
            //取出当前路径所有关系实例数据
            List<CcCiRltInfo> rltList = rltGroup.get(rank.getPath());
            if(BinaryUtils.isEmpty(rltList)){
                continue;
            }
            for (CcCiRltInfo each : rltList) {
                String sourceCiCode = each.getSourceCiInfo().getCi().getCiCode();
                String targetCiCode = each.getTargetCiInfo().getCi().getCiCode();
                Long sourceClassId = each.getSourceCiInfo().getCi().getClassId();
                Long targetClassId = each.getTargetCiInfo().getCi().getClassId();
                if(sourceCiCode.equals(targetCiCode) || sourceClassId.equals(attrClassId) || targetClassId.equals(attrClassId)){
                    continue;
                }
                //根节点
                String rootCiCode = from ?targetCiCode:sourceCiCode;
                DataAnalyzeBatch analyze = new DataAnalyzeBatch(each);
                analyze.setLeaf(leaf);
                analyze.setRltClass(rltClassMap.get(analyze.getRlt().getClassId()));
                analyzeGroup.computeIfAbsent(rootCiCode, key->new ArrayList<>()).add(analyze);
            }
        }
        //去除循环节点情况
        Set<String> filterSet = new HashSet<>();
        this.filterByRoot(Sets.newHashSet(ciCode), analyzeGroup, result, from, filterSet);
        return result;
    }
    private void filterByRoot(Set<String> parentCodes, Map<String, List<DataAnalyzeBatch>> analyzeGroup, List<DataAnalyzeBatch> filterList, boolean from, Set<String> filterSet){
        Set<String> childCodes = new HashSet<>();
        for (String parentCode : parentCodes) {
            List<DataAnalyzeBatch> childLink = analyzeGroup.get(parentCode);
            if(filterSet.contains(parentCode) || CollectionUtils.isEmpty(childLink)){
                continue;
            }
            if(from){
                childCodes.addAll(childLink.stream().map(DataAnalyzeBatch::getSourceCiCode).collect(Collectors.toSet()));
            }else{
                childCodes.addAll(childLink.stream().map(DataAnalyzeBatch::getTargetCiCode).collect(Collectors.toSet()));
            }
            filterSet.add(parentCode);
            filterList.addAll(childLink);
        }
        if(CollectionUtils.isEmpty(childCodes)){
            return;
        }
        this.filterByRoot(childCodes, analyzeGroup, filterList, from, filterSet);
    }

    /**
     * 以当前key为源端或者目标端，查找下一条线路
     * @param keys 节点key
     * @param linkGroup 关系线分组
     * @param linkList 返回集合
     * @param distinctLink 去重关系线集合
     * @param from 是否源端
     */
    private void findNextPath(Set<Long> keys, Map<Long, List<VisualModelLink>> linkGroup, List<VisualModelLink> linkList, Set<VisualModelLink> distinctLink, boolean from){
        List<VisualModelLink> nextLinks = keys.stream().map(linkGroup::get).filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
        //去除循环节点
        nextLinks.removeAll(distinctLink);
        if(BinaryUtils.isEmpty(nextLinks)){
            return;
        }
        Set<Long> nextKeys;
        if(from){
            nextKeys = nextLinks.stream().map(VisualModelLink::getTo).collect(Collectors.toSet());
        }else{
            nextKeys = nextLinks.stream().map(VisualModelLink::getFrom).collect(Collectors.toSet());
        }
        linkList.addAll(nextLinks);
        distinctLink.addAll(nextLinks);
        findNextPath(nextKeys, linkGroup, linkList, distinctLink, from);
    }

    private List<RltDataAnalyzeDto> queryAnalyze(String rootCiCode, List<String> modelRlt, Integer position){
        List<RltDataAnalyzeDto> result = new ArrayList<>();
        List<CcCiRltInfo> rltInfoList;
        if(RltPositionEnum.LEFT.getVal() == position){
            rltInfoList = rltSwitchSvc.queryRltByCodes(null, Sets.newHashSet(rootCiCode), null, null);
        }else{
            rltInfoList = rltSwitchSvc.queryRltByCodes(Sets.newHashSet(rootCiCode), null, null, null);
        }
        if (BinaryUtils.isEmpty(rltInfoList)) {
            return result;
        }
        Set<Long> rltClassIds = rltInfoList.stream().map(each->each.getCiRlt().getClassId()).collect(Collectors.toSet());
        //获取关系分类映射
        Map<Long, CcCiClassInfo> rltClassMap = getRltClassMap(rltClassIds);
        Set<String> ciCodes;
        if(RltPositionEnum.LEFT.getVal() == position){
            ciCodes = rltInfoList.stream().map(each -> each.getSourceCiInfo().getCi().getCiCode()).collect(Collectors.toSet());
        }else{
            ciCodes = rltInfoList.stream().map(each -> each.getTargetCiInfo().getCi().getCiCode()).collect(Collectors.toSet());
        }
        ESCIClassInfo attrClass = eamClassApiSvc.getCIClassByCode(Env.ATTRIBUTES.getCode());
        Long attrClassId = 1l;
        if (attrClass != null) {
            attrClassId = attrClass.getId();
        }
        Long finalAttrClassId = attrClassId;
        Map<String, List<CcCiRltInfo>> sourceCountMap = getCountMap(ciCodes, RltPositionEnum.LEFT.getVal());
        Map<String, List<CcCiRltInfo>> targetCountMap = getCountMap(ciCodes, RltPositionEnum.RIGHT.getVal());
        for (CcCiRltInfo rltInfo : rltInfoList) {
            String rootKey = rltInfo.getSourceCiInfo().getCiClass().getId()+"_"+rltInfo.getCiRlt().getClassId()+"_"+rltInfo.getTargetCiInfo().getCiClass().getId();
            if(!modelRlt.contains(rootKey)){
                continue;
            }
            RltDataAnalyzeDto analyze = new RltDataAnalyzeDto();
            analyze.setRltInfo(rltInfo.getCiRlt());
            analyze.setRltCode(rltInfo.getCiRlt().getCiCode());
            CcCiInfo ciInfo;
            if(RltPositionEnum.LEFT.getVal() == position){
                ciInfo = rltInfo.getSourceCiInfo();
            }else{
                ciInfo = rltInfo.getTargetCiInfo();
            }
            //过滤源端和目标端ci相同的关系
            if(ciInfo.getCi().getCiCode().equals(rootCiCode)){
                continue;
            }
            ciInfo.setFixMapping(null);
            EamAnalyseCiVo copy = EamUtil.copy(ciInfo, EamAnalyseCiVo.class);
            analyze.setNodeInfo(copy);
            analyze.setRltClassInfo(rltClassMap.get(rltInfo.getCiRlt().getClassId()));
            String ciCode = ciInfo.getCi().getCiCode();
            //统计数量在只展开左边或右边的情况下,排除实体属性
            List<CcCiRltInfo> sourceList = sourceCountMap.getOrDefault(ciCode, Lists.newArrayList()).stream().filter(rlt -> {
                String key = rlt.getSourceCiInfo().getCi().getClassId() + "_" + rlt.getCiRlt().getClassId() + "_" + rlt.getTargetCiInfo().getCi().getClassId();
                boolean notAttr = !rlt.getSourceCiInfo().getCi().getClassId().equals(finalAttrClassId);
                return modelRlt.contains(key) && !rlt.getSourceCiInfo().getCi().getCiCode().equals(ciCode) && notAttr;
            }).collect(Collectors.toList());
            analyze.setSourceCount(sourceList.size());

            List<CcCiRltInfo> targetList = targetCountMap.getOrDefault(ciCode, Lists.newArrayList()).stream().filter(rlt -> {
                String key = rlt.getSourceCiInfo().getCi().getClassId() + "_" + rlt.getCiRlt().getClassId() + "_" + rlt.getTargetCiInfo().getCi().getClassId();
                boolean notAttr = !rlt.getTargetCiInfo().getCi().getClassId().equals(finalAttrClassId);
                return modelRlt.contains(key) && !rlt.getTargetCiInfo().getCi().getCiCode().equals(ciCode) && notAttr;
            }).collect(Collectors.toList());
            analyze.setTargetCount(targetList.size());
            result.add(analyze);
        }
        return result;
    }

    private Map<String, List<CcCiRltInfo>> getCountMap(Set<String> ciCodes, Integer position){
        if (BinaryUtils.isEmpty(ciCodes)) {
            return Collections.emptyMap();
        }
        List<CcCiRltInfo> rltInfoList;
        if(RltPositionEnum.LEFT.getVal() == position){
            rltInfoList = rltSwitchSvc.queryRltByCodes(null, ciCodes, null, null);
        }else{
            rltInfoList = rltSwitchSvc.queryRltByCodes(ciCodes, null, null, null);
        }
        if(BinaryUtils.isEmpty(rltInfoList)){
            return Collections.emptyMap();
        }
        if(RltPositionEnum.LEFT.getVal() == position){
            return rltInfoList.stream().collect(Collectors.groupingBy(each->each.getTargetCiInfo().getCi().getCiCode()));
        }else{
            return rltInfoList.stream().collect(Collectors.groupingBy(each->each.getSourceCiInfo().getCi().getCiCode()));
        }
    }

    private Map<Long, CcCiClassInfo> getRltClassMap(Set<Long> rltClassIds) {
        CCcCiClass cdt = new CCcCiClass();
        cdt.setIds(rltClassIds.toArray(new Long[]{}));
        List<CcCiClassInfo> rltClassList = rltClassSvc.getRltClassByCdt(cdt);
        return rltClassList.stream().collect(Collectors.toMap(ccCiClassInfo -> ccCiClassInfo.getCiClass().getId(), ccCiClassInfo -> ccCiClassInfo, (k1, k2) -> k1));
    }

    private void setAttribute(List<RltDataAnalyzeDto> analyzeList, List<Long> entityClassIds, Long attrClassId){
        Set<String> entityCodes = new HashSet<>();
        List<RltDataAnalyzeDto> removeList = new ArrayList<>();
        for (RltDataAnalyzeDto each : analyzeList) {
            if(entityClassIds.contains(each.getNodeInfo().getCi().getClassId())){
                entityCodes.add(each.getNodeInfo().getCi().getCiCode());
            }
            if(attrClassId != null && attrClassId.equals(each.getNodeInfo().getCi().getClassId())){
                removeList.add(each);
            }
        }
        analyzeList.removeAll(removeList);
        if(CollectionUtils.isEmpty(entityCodes)){
            return;
        }
        if(attrClassId == null){
            return;
        }
        List<CcCiRltInfo> rltInfoList = rltSwitchSvc.queryRltByCodes(entityCodes, null, entityClassIds, Lists.newArrayList(attrClassId));
        if(CollectionUtils.isEmpty(rltInfoList)){
            return;
        }
        Map<String, List<CcCiRltInfo>> rltGroup = rltInfoList.stream().collect(Collectors.groupingBy(e -> e.getCiRlt().getSourceCiCode()));
        for (RltDataAnalyzeDto each : analyzeList) {
            List<CcCiRltInfo> attrRltList = rltGroup.get(each.getNodeInfo().getCi().getCiCode());
            if(CollectionUtils.isEmpty(attrRltList)){
                continue;
            }
            List<CcCiInfo> attrList = attrRltList.stream().map(CcCiRltInfo::getTargetCiInfo).distinct().collect(Collectors.toList());
            each.getNodeInfo().setAttrList(attrList);
        }
    }

    @Override
    public List<EamAnalyseCiVo> queryByCiCodes(List<String> ciCodes) {
        SysUser user = SysUtil.getCurrentUserInfo();
        //增加元模型限制
        ESVisualModel esVisualModel = esVisualModelSvc.getEnableModel(user.getDomainId());
        if(BinaryUtils.isEmpty(esVisualModel)){
            return Collections.emptyList();
        }
        List<DiagramNodeLinkInfo> rltLinkList = VisualModelUtils.getRltClassIds(esVisualModel);
        List<String> modelRlt = rltLinkList.stream().map(DiagramNodeLinkInfo::getLinkKey).distinct().collect(Collectors.toList());
        if(BinaryUtils.isEmpty(modelRlt)){
            return Collections.emptyList();
        }
        if(BinaryUtils.isEmpty(ciCodes)){
            return Collections.emptyList();
        }
        List<ESCIInfo> ciList = iciSwitchSvc.getCiByCodes(ciCodes, null, LibType.DESIGN);
        if(BinaryUtils.isEmpty(ciList)){
            return Collections.emptyList();
        }
        CCcCiClass classQuery = new CCcCiClass();
        classQuery.setDomainId(user.getDomainId());
        List<CcCiClassInfo> classList = ciClassApiSvc.queryClassByCdt(classQuery);
        Map<Long, CcCiClassInfo> classIdMap = classList.stream().collect(Collectors.toMap(each -> each.getCiClass().getId(), each -> each, (k1,k2)->k2));
        Map<String, CcCiClassInfo> classCodeMap = classList.stream().collect(Collectors.toMap(each -> each.getCiClass().getClassCode(), each -> each, (k1,k2)->k2));

        CcCiClassInfo attrClass = classCodeMap.get(Env.ATTRIBUTES.getCode());
        CcCiClassInfo conceptionClass = classCodeMap.get(Env.CONCEPTION_ENTITY.getCode());
        CcCiClassInfo logicClass = classCodeMap.get(Env.LOGIC_ENTITY.getCode());
        CcCiClassInfo physicalClass = classCodeMap.get(Env.PHYSICAL_ENTITY.getCode());
        List<Long> entityClassIds = new ArrayList<>();
        if (conceptionClass != null && conceptionClass.getCiClass() != null) {
            entityClassIds.add(conceptionClass.getCiClass().getId());
        }
        if (logicClass != null && logicClass.getCiClass() != null) {
            entityClassIds.add(logicClass.getCiClass().getId());
        }
        if (physicalClass != null && physicalClass.getCiClass() != null) {
            entityClassIds.add(physicalClass.getCiClass().getId());
        }
        Long attrClassId = 1l;
        if (attrClass != null && attrClass.getCiClass() != null) {
            attrClassId = attrClass.getCiClass().getId();
        }
        Long finalAttrClassId = attrClassId;
        Set<String> queryCodes = Sets.newHashSet(ciCodes);
        List<CcCiRltInfo> fromRltInfoList = rltSwitchSvc.queryRltByCodes(queryCodes, null, null, null);
        Map<String, Long> fromGroup = fromRltInfoList.stream().filter(rlt -> {
            String key = rlt.getSourceCiInfo().getCi().getClassId() + "_" + rlt.getCiRlt().getClassId() + "_" + rlt.getTargetCiInfo().getCi().getClassId();
            return finalAttrClassId != null && !finalAttrClassId.equals(rlt.getTargetCiInfo().getCi().getClassId()) && modelRlt.contains(key);
        }).collect(Collectors.groupingBy(each -> each.getSourceCiInfo().getCi().getCiCode(), Collectors.counting()));

        List<CcCiRltInfo> toRltInfoList = rltSwitchSvc.queryRltByCodes(null, queryCodes, null, null);
        Map<String, Long> toGroup = toRltInfoList.stream().filter(rlt -> {
            String key = rlt.getSourceCiInfo().getCi().getClassId() + "_" + rlt.getCiRlt().getClassId() + "_" + rlt.getTargetCiInfo().getCi().getClassId();
            return finalAttrClassId != null && !finalAttrClassId.equals(rlt.getTargetCiInfo().getCi().getClassId()) && modelRlt.contains(key);
        }).collect(Collectors.groupingBy(each -> each.getTargetCiInfo().getCi().getCiCode(), Collectors.counting()));


        List<EamAnalyseCiVo> result = new ArrayList<>();
        Set<String> entityCodes = new HashSet<>();
        for (ESCIInfo ciInfo : ciList) {
            if(entityClassIds.contains(ciInfo.getClassId())){
                entityCodes.add(ciInfo.getCiCode());
            }
            CcCiClassInfo classInfo = classIdMap.get(ciInfo.getClassId());
            CcCiInfo ci = EamUtil.coverESCIInfo(ciInfo, EamUtil.copy(classInfo.getAttrDefs(), CcCiAttrDef.class), classInfo.getCiClass());
            EamAnalyseCiVo analyseCi = EamUtil.copy(ci, EamAnalyseCiVo.class);
            analyseCi.setTargetCount(fromGroup.getOrDefault(ciInfo.getCiCode(), 0L));
            analyseCi.setSourceCount(toGroup.getOrDefault(ciInfo.getCiCode(), 0L));
            result.add(analyseCi);
        }
        if(!CollectionUtils.isEmpty(entityCodes)){
            if (attrClassId == null) {
                attrClassId = 1l;
            }
            List<CcCiRltInfo> rltInfoList = rltSwitchSvc.queryRltByCodes(entityCodes, null, entityClassIds, Lists.newArrayList(attrClassId));
            if(!CollectionUtils.isEmpty(rltInfoList)){
                Map<String, List<CcCiRltInfo>> rltGroup = rltInfoList.stream().collect(Collectors.groupingBy(e -> e.getCiRlt().getSourceCiCode()));
                for (EamAnalyseCiVo each : result) {
                    List<CcCiRltInfo> attrRltList = rltGroup.get(each.getCi().getCiCode());
                    if(CollectionUtils.isEmpty(attrRltList)){
                        continue;
                    }
                    List<CcCiInfo> attrList = attrRltList.stream().map(CcCiRltInfo::getTargetCiInfo).distinct().collect(Collectors.toList());
                    each.setAttrList(attrList);
                }
            }
        }
        return result;
    }

    @Override
    public List<DataAnalyzeBatch> queryPathOneSide(String ciCode, AnalyseLeaf leaf) {
        AnalysePathDto analysePathDto = queryPath(ciCode);
        if(BinaryUtils.isEmpty(analysePathDto)){
            return new ArrayList<>();
        }
        //from:查询上游；to:false=查询下游
        boolean from = AnalyseLeaf.FROM.equals(leaf);
        Object linkObj;
        List<AnalyseLinkVo> linkVo = new ArrayList<>();
        if(from){
            if(BinaryUtils.isEmpty(analysePathDto.getUp())){
                return new ArrayList<>();
            }
            linkObj = analysePathDto.getUp().get(LINK_LIST);
        }else{
            //to
            if(BinaryUtils.isEmpty(analysePathDto.getDown())){
                return new ArrayList<>();
            }
            linkObj = analysePathDto.getDown().get(LINK_LIST);
        }
        if(!BinaryUtils.isEmpty(linkObj)){
            linkVo = JSON.parseArray(JSON.toJSONString(linkObj), AnalyseLinkVo.class);
        }
        List<String> path = linkVo.stream().map(AnalyseLinkVo::getCode).filter(Objects::nonNull).collect(Collectors.toList());
        return queryByPath(ciCode, leaf, path);
    }

    @Override
    public ResponseEntity<byte[]> export(String rootCode, List<String> ciCodeList, List<String> rltCodeList) {
        List<EamAnalyseTableVo> tableList = this.getTable(rootCode, ciCodeList, rltCodeList);
        List<ESCIInfo> ciList = iciSwitchSvc.getCiByCodes(Collections.singletonList(rootCode), null, LibType.DESIGN);
        ESCIInfo root = ciList.get(0);
        SXSSFWorkbook workbook = new SXSSFWorkbook(new XSSFWorkbook());
        AtomicInteger sheetNum = new AtomicInteger(0);
        EamAnalyseTableVo upData = tableList.get(0);
        EamAnalyseTableVo downData = tableList.get(1);
        int totalRow = tableList.get(0).getData().size() + tableList.get(1).getData().size();
        //创建一个新的sheet页
        if(upData.getData().size() > 1 || downData.getData().size() == 1){
            SXSSFSheet sheet1 = workbook.createSheet("上游信息表" + sheetNum.incrementAndGet());
            writeToExcel(workbook, sheet1, root, tableList.get(0), totalRow);
        }
        if(downData.getData().size() > 1){
            SXSSFSheet sheet2 = workbook.createSheet("下游信息表" + sheetNum.incrementAndGet());
            writeToExcel(workbook, sheet2, root, tableList.get(1), totalRow);
        }
        String label = root.getCiLabel().replaceAll("[\\[\\]\\\\\"]", "") + "】";
        String fileName = "关联分析" + CommUtil.EXCEL07_XLSX_EXTENSION;
        File file = ExcelUtil.writeExcel(workbook, fileName);
        return ExcelUtil.returnRes(file);
    }

    private void writeToExcel(Workbook workbook, SXSSFSheet sheet, ESCIInfo root, EamAnalyseTableVo table, int totalRow) {
        CTWorksheet sheetX = sheet.getWorkbook().getXSSFWorkbook().getSheetAt(0).getCTWorksheet();
        //1.标题行
        AtomicInteger rowNum = new AtomicInteger(0);
        Row titleRow = sheet.createRow(rowNum.getAndIncrement());
        Cell titleCell = ExcelUtil.createCell(titleRow, ExcelUtil.getTitleStyle(workbook), table.getTitle(), 0);
        sheet.addMergedRegion(new CellRangeAddress(titleCell.getRowIndex(), titleCell.getRowIndex(), 0, table.getMaxColNum()));
        //2.表头行
        Row headerRow = sheet.createRow(rowNum.getAndIncrement());
        ExcelUtil.createCell(headerRow, ExcelUtil.getHeaderStyle(workbook), STR_NUM, 0);

        List<String> headers = new ArrayList<>();
        for (int i = 1; i <= table.getMaxColNum(); i++) {
            sheet.setColumnWidth(i, 40 * 256);
            String value = i + "级";
            headers.add(value);
            ExcelUtil.createCell(headerRow, ExcelUtil.getHeaderStyle(workbook), value, i);
        }
        List<ESCIInfo> rootList = Collections.singletonList(root);
        //3.按行写入数据
        AtomicInteger dataRowNum = new AtomicInteger(0);
        CellStyle simpleStyle = ExcelUtil.getSimpleStyle(workbook);
        for (Map<String, String> dataMap : table.getData()) {
            Row dataRow = sheet.createRow(rowNum.getAndIncrement());
            AtomicInteger dataColNum = new AtomicInteger(0);
            //先放入序号
            ExcelUtil.createCell(dataRow, null, String.valueOf(dataRowNum.incrementAndGet()), dataColNum.getAndIncrement());
            headers.forEach(header -> ExcelUtil.createCell(dataRow, simpleStyle, dataMap.get(header), dataColNum.getAndIncrement()));
        }
        /*for (Map.Entry<Integer, List<EamAnalyseMergeVo>> entry : table.getMergeMap().entrySet()) {
            int colNum = entry.getKey() + 1;
            List<EamAnalyseMergeVo> mergeList = entry.getValue();
            for (EamAnalyseMergeVo merge : mergeList) {
                //一行以内的内容或总行数大于一万先不做合并
                if(merge.getCount().get() <= 1 || totalRow > 10000){
                    continue;
                }
                sheet.addMergedRegion(new CellRangeAddress(merge.getStartNum() + 2, merge.getEndNum() + 2, colNum, colNum));
            }
        }*/
    }

    /**
     * 计算所有路线
     */
    private void computePaths(Map<String, List<ESCIInfo>> parentNodeMap, Map<String, ESCIInfo> ciMap, Map<String, List<ESCIRltInfo>> rltGroup, Map<Long, String> classNameMap,
                              Map<String, List<CiSimpleInfoVo>> parentPathMap, List<List<CiSimpleInfoVo>> paths, List<String> distinct, Boolean direction) {
        if(CollectionUtils.isEmpty(parentNodeMap)){
            return;
        }
        //ciCode路线->子集
        Map<String, List<ESCIInfo>> nextNodes = new HashMap<>();
        Map<String, List<CiSimpleInfoVo>> childPathMap = new HashMap<>();
        for (Map.Entry<String, List<ESCIInfo>> entry : parentNodeMap.entrySet()) {
            String parentCode = entry.getKey();
            List<CiSimpleInfoVo> parentPath = parentPathMap.computeIfAbsent(parentCode, key -> new ArrayList<>());
            for (ESCIInfo node : entry.getValue()) {
                String name = node.getCiLabel().replaceAll("[\\[\\]\\\\\"]", "");
                String className = classNameMap.get(node.getClassId());
                CiSimpleInfoVo info = new CiSimpleInfoVo(node.getId(), name, node.getCiCode(), node.getClassId(), className);
                info.setCirculate(parentCode.contains(node.getCiCode()));
                List<CiSimpleInfoVo> childPath = new ArrayList<>(parentPath);
                //超过10级不再处理
                if(info.getCirculate() || parentCode.split("_").length >= 15){
                    paths.add(childPath);
                    continue;
                }
                //路径加入当前节点
                childPath.add(info);
                String currCode = parentCode + "_" + node.getCiCode();
                childPathMap.put(currCode, childPath);
                List<ESCIRltInfo> rltList = rltGroup.getOrDefault(node.getCiCode(), Lists.newArrayList());
                if(CollectionUtils.isEmpty(rltList)){
                    paths.add(childPath);
                }else{
                    List<ESCIInfo> childCiList = new ArrayList<>();
                    for (ESCIRltInfo rltInfo : rltList) {
                        String childCiCode = direction ? rltInfo.getTargetCiCode() : rltInfo.getSourceCiCode();
                        ESCIInfo childNode = ciMap.get(childCiCode);
                        if(childNode == null){
                            continue;
                        }
                        childCiList.add(childNode);
                    }
                    if(CollectionUtils.isEmpty(childCiList)){
                        continue;
                    }
                    nextNodes.put(currCode, childCiList);
                }
            }
        }
        computePaths(nextNodes, ciMap, rltGroup, classNameMap, childPathMap, paths, distinct, direction);
    }

    @Override
    public List<EamAnalyseTableVo> getTable(String rootCode, List<String> ciCodeList, List<String> rltCodeList) {
        List<ESCIInfo> ciList = iciSwitchSvc.getCiByCodes(ciCodeList, null, LibType.DESIGN);
        if(CollectionUtils.isEmpty(ciList)){
            throw new ServiceException("没有可导出的数据");
        }
        Map<String, ESCIInfo> ciMap = ciList.stream().collect(Collectors.toMap(CcCi::getCiCode, e -> e, (k1, k2) -> k2));
        List<Long> classIds = ciList.stream().map(CcCi::getClassId).distinct().collect(Collectors.toList());
        List<ESCIClassInfo> classList = ciClassApiSvc.queryESClassInfoByIds(classIds);
        Map<Long, String> classNameMap = classList.stream().collect(Collectors.toMap(CcCiClass::getId, CcCiClass::getClassName, (k1, k2) -> k2));
        List<ESCIRltInfo> rltInfoList = new ArrayList<>();
        if(!CollectionUtils.isEmpty(rltCodeList)){
            rltInfoList = rltSwitchSvc.getRltByUniqueCodes(new HashSet<>(rltCodeList), null, LibType.DESIGN);
        }
        Map<String, List<ESCIRltInfo>> sourceGroup = rltInfoList.stream().collect(Collectors.groupingBy(CcCiRlt::getSourceCiCode));
        Map<String, List<ESCIRltInfo>> targetGroup = rltInfoList.stream().collect(Collectors.groupingBy(CcCiRlt::getTargetCiCode));
        Workbook workbook = new SXSSFWorkbook(new XSSFWorkbook());
        //创建一个新的sheet页
        ESCIInfo root = ciMap.get(rootCode);
        EamAnalyseTableVo upData = writeToTable(root, ciMap, targetGroup, classNameMap, new AtomicInteger(0), false);
        EamAnalyseTableVo downData = writeToTable(root, ciMap, sourceGroup, classNameMap, new AtomicInteger(0), true);

        return Lists.newArrayList(upData, downData);
    }

    private EamAnalyseTableVo writeToTable(ESCIInfo root, Map<String, ESCIInfo> ciMap, Map<String, List<ESCIRltInfo>> rltGroup,
                              Map<Long, String> classNameMap, AtomicInteger rowNum, Boolean direction) {
        List<List<CiSimpleInfoVo>> paths = new ArrayList<>();
        Map<String, List<ESCIInfo>> parentNodeMap = new HashMap<>();
        parentNodeMap.put("root", Lists.newArrayList(root));
        Map<String, List<CiSimpleInfoVo>> pathMap = new HashMap<>();
        computePaths(parentNodeMap, ciMap, rltGroup, classNameMap, pathMap, paths, new ArrayList<>(), direction);
        EamAnalyseTableVo result = new EamAnalyseTableVo();
        int maxColNum = paths.stream().mapToInt(List::size).max().orElse(0);
        //1.标题行
        String tableName = direction ? "下游信息表" : "上游信息表";
        String title = classNameMap.get(root.getClassId()) + "【" + root.getCiLabel().replaceAll("[\\[\\]\\\\\"]", "") + "】" + tableName;
        result.setTitle(title);
        result.setMaxColNum(maxColNum);
        //3.按行写入数据
        AtomicInteger dataRowNum = new AtomicInteger(0);
        //<列数, 已合并到的最大行数>记录每列已合并的最大行,避免重复合并
        Map<Integer, List<EamAnalyseMergeVo>> mergeMap = new HashMap<>();
        List<Map<String, String>> data = new ArrayList<>();
        for (List<CiSimpleInfoVo> path : paths) {
            int dataRow = dataRowNum.getAndIncrement();
            Map<String, String> map = new HashMap<>();
            for (int i = 0; i < maxColNum; i++) {
                String value = "/";
                int index = direction ? i : maxColNum - i - 1;
                if (index < path.size()) {
                    CiSimpleInfoVo ciInfo = path.get(index);
                    value = ciInfo.getName() + "(" + ciInfo.getClassName() + ")";
                }
                map.put((i+1)+"级", value);
                /*List<EamAnalyseMergeVo> mergeList = mergeMap.computeIfAbsent(i, key -> new ArrayList<>());
                //查看是否当前列第一条数据
                if(CollectionUtils.isEmpty(mergeList) || "/".equals(value)){
                    mergeList.add(new EamAnalyseMergeVo(value, dataRow));
                    continue;
                }
                //取出最后一个看看是否已终止合并, 若已终止则新建一个合并对象, 否则对比value值,若不一样则终止合并
                EamAnalyseMergeVo lastMerge = mergeList.get(mergeList.size() - 1);
                if(lastMerge.getTheEnd()){
                    mergeList.add(new EamAnalyseMergeVo(value, dataRow));
                } else if(value.equals(lastMerge.getName())) {
                    lastMerge.setEndNum(dataRow);
                    lastMerge.getCount().incrementAndGet();
                } else {
                    lastMerge.setTheEnd(true);
                    mergeList.add(new EamAnalyseMergeVo(value, dataRow));
                }*/
            }
            data.add(map);
        }
        result.setData(data);
//        result.setMergeMap(mergeMap);
        return result;
    }
}
